Extract rule: template-no-restricted-invocations#2570
Conversation
NullVoxPopuli-ai-agent
left a comment
There was a problem hiding this comment.
Review: template-no-restricted-invocations
Compared with the original ember-template-lint no-restricted-invocations rule.
What looks good
- Comprehensive implementation covering all invocation types:
MustacheStatement,BlockStatement,ElementNode,SubExpression, andModifierStatement. - Correct
dasherizefunction that handles::namespace separators (e.g.,NestedScope::FooBar→nested-scope/foo-bar). - Block param scope tracking with push/pop correctly prevents false positives on yielded names.
- Custom error message support via the
{ names, message }object config format matches the original. componenthelper detection works for both mustache and subexpression forms.- Extensive test suite covering GJS and HBS modes, with tests for modifiers, nested subexpressions, component helper, block params, nested scopes, and custom messages.
Potential issues
-
parseConfigcalled but result offalsenot fully handled: WhenparseConfigreturnsfalse, thecreatemethod returns{}(no-op). However, when config istrue, it returns[](empty array). The original rule throws on invalid config; here passingtruewith no items would silently do nothing. This is fine behavior but differs slightly from the original which would also accepttruewith an error. Minor difference. -
Original uses
this.isLocal(node)for all node types: The original checksthis.isLocal(node)at the top of the unifiedcheckDenylistfunction, which leverages the base class's scope tracking. The PR re-implements scope tracking manually withblockParamScopes. This is necessary since ESLint rules don't have theisLocalbase class, but worth verifying the scope tracking is complete:GlimmerBlockStatementpushesnode.program.blockParams✅GlimmerElementNodepushesnode.blockParams✅- Both have
:exithandlers that pop ✅ - The
isBlockParamcheck is applied before restriction checks ✅
This looks correct.
-
Original validates config items with
isDasherizedComponentOrHelperName: The original enforces that config items are valid dasherized names. The ESLint version relies on schema validation but the schema only checkstype: 'string'-- it doesn't enforce the dasherized naming convention. This means invalid names like"Foo Bar"would be accepted by the schema. Minor difference, but could be worth noting in docs. -
Error message format differences for SubExpression: The original uses
{{name}}format for the display name of all non-ElementNode types. The PR uses different formats:(name)for SubExpression,{{name}}for Mustache/Block/Modifier. Looking at the tests, SubExpression errors show'(foo)'and'(component "foo")'which matches the original's behavior of using parentheses for subexpressions. Actually, the original source shows it uses{{name}}for all non-ElementNode types. The PR diverges by using(name)for subexpressions. This seems like a deliberate improvement to better represent the actual syntax. -
ElementNode modifier checking: The PR checks
node.modifiersinsideGlimmerElementNodevisitor. This is a good approach since modifiers aren't separate visitors in the Glimmer AST -- they're properties of the element node. However, the PR also has a separateGlimmerModifierStatementvisitor. This could potentially result in double-reporting if both fire for the same modifier. Worth verifying thatGlimmerModifierStatementis actually emitted by the parser, or if modifiers are only accessible vianode.modifierson elements. -
Test comprehensiveness: Excellent coverage. The HBS tests mirror all the patterns from the original test file including nested subexpressions, component helper variants, and custom messages.
Minor
/* eslint-disable */comments at top and bottom are a pragmatic choice for rule files that need different style.- The
originallyFrommetadata is a nice touch for traceability.
Very solid port with thorough test coverage. The main concern is potential double-reporting of modifiers (#5).
🤖 Automated review comparing with ember-template-lint source
When in gjs/gts mode, names that resolve to JS-scope variables (imports, const, let, function params) are now treated the same as block params and exempted from restriction checks. Previously, block params were exempt but JS-scope bindings were not, which was inconsistent -- both are local bindings that the developer explicitly controls. Uses sourceCode.getScope(node) to check whether a reference resolves to an actual variable definition (ref.resolved != null), ensuring that ambient/global names are still flagged. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
7e2b508 to
5234d16
Compare
Split from #2371.